/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
package org.apache.asyncweb.server.context;

import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.asyncweb.common.DefaultHttpResponse;
import org.apache.asyncweb.common.HttpHeaderConstants;
import org.apache.asyncweb.common.HttpRequest;
import org.apache.asyncweb.common.HttpResponse;
import org.apache.asyncweb.common.HttpResponseStatus;
import org.apache.asyncweb.common.MutableHttpResponse;
import org.apache.asyncweb.server.HttpClientListener;
import org.apache.asyncweb.server.HttpServiceContext;
import org.apache.asyncweb.server.HttpSession;
import org.apache.asyncweb.server.ServiceContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A default implementation of {@link HttpServiceContext}.
 * 
 * @author The Apache MINA Project (dev@mina.apache.org)
 */
public abstract class AbstractHttpServiceContext implements HttpServiceContext {
	private final Logger LOG = LoggerFactory
			.getLogger(AbstractHttpServiceContext.class);

	private final InetSocketAddress remoteAddress;

	private final InetSocketAddress localAddress;

	private final HttpRequest request;

	private HttpResponse committedResponse;

	private HttpSession session;

	private boolean createdSession;

	private final ServiceContainer container;

	private final List<HttpClientListener> listeners = new CopyOnWriteArrayList<HttpClientListener>();

	private ReentrantLock lock = new ReentrantLock();

	public AbstractHttpServiceContext(InetSocketAddress localAddress,
			InetSocketAddress remoteAddress, HttpRequest request,
			ServiceContainer container) {
		if (remoteAddress == null) {
			throw new NullPointerException("remoteAddress");
		}

		if (localAddress == null) {
			throw new NullPointerException("localAddress");
		}

		if (request == null) {
			throw new NullPointerException("request");
		}

		if (container == null) {
			throw new NullPointerException("container");
		}

		this.remoteAddress = remoteAddress;
		this.localAddress = localAddress;
		this.request = request;
		this.container = container;
		this.session = container.getSessionAccessor().getSession(this, false);
	}

	public boolean isResponseCommitted() {
		lock.lock();
		try {
			return committedResponse != null;
		} finally {
			lock.unlock();
		}
	}

	/**
	 * Commits a <code>HttpResponse</code> to this <code>Request</code>.
	 * 
	 * @param response
	 *            The response to commit
	 * @return <code>true</code> iff the response was committed
	 */
	public boolean commitResponse(HttpResponse response) {
		lock.lock();
		try {
			if (committedResponse != null) {
				if (LOG.isInfoEnabled())
					LOG.info("Request already comitted to a response. Disposing response");
				return false;
			}
			committedResponse = response;
		} finally {
			lock.unlock();
		}

		MutableHttpResponse mreponse = (MutableHttpResponse) response;
		// Add the session identifier if the session was newly created.
		if (createdSession) {
			container.getSessionAccessor().addSessionIdentifier(this, mreponse);
		}

		// Only parsed requests can be formatted.
		if (getRequest().getMethod() != null) {
			container.getErrorResponseFormatter().formatResponse(getRequest(),
					mreponse);
		}

		if (container.isSendServerHeader()) {
			mreponse.setHeader(HttpHeaderConstants.KEY_SERVER, "AsyncWeb");
		}

		// Normalize the response.
		mreponse.normalize(getRequest());

		// Override connection header if needed.
		if (!container.getKeepAliveStrategy().keepAlive(this, response)) {
			mreponse.setHeader(HttpHeaderConstants.KEY_CONNECTION,
					HttpHeaderConstants.VALUE_CLOSE);
		}

		boolean requiresClosure = !HttpHeaderConstants.VALUE_KEEP_ALIVE
				.equalsIgnoreCase(response
						.getHeader(HttpHeaderConstants.KEY_CONNECTION));

		if (requiresClosure && LOG.isDebugEnabled()) {
			LOG.debug("Response status: " + response.getStatus());
			LOG.debug("Keep-alive strategy requires closure of "
					+ getRemoteAddress());
		}

		if (LOG.isDebugEnabled()) {
			LOG.debug("Committing a response:");
			LOG.debug("Status: " + response.getStatus() + ' '
					+ response.getStatusReasonPhrase());
			LOG.debug("Headers: " + response.getHeaders());
		}

		doWrite(requiresClosure);
		return true;
	}

	public boolean commitResponse(HttpResponseStatus status) {
		MutableHttpResponse response = new DefaultHttpResponse();
		response.setStatus(status);
		return commitResponse(response);
	}

	public synchronized HttpResponse getCommittedResponse() {
		return committedResponse;
	}

	public InetSocketAddress getRemoteAddress() {
		return remoteAddress;
	}

	public InetSocketAddress getLocalAddress() {
		return localAddress;
	}

	public HttpRequest getRequest() {
		return request;
	}

	public HttpSession getSession() {
		return getSession(true);
	}

	public HttpSession getSession(boolean create) {
		lock.lock();
		try {
			if (session != null && !session.isValid()) {
				session = null;
			}

			if (session == null) {
				session = container.getSessionAccessor().getSession(this,
						create);
				if (create) {
					createdSession = true;
				}
			}

			return session;
		} finally {
			lock.unlock();
		}
	}

	public void fireClientDisconnected() {

		Iterator<HttpClientListener> it = listeners.iterator();
		while (it.hasNext()) {
			it.next().clientDisconnected(this);
		}
	}

	public void fireClientIdle(long idleTime, int idleCount) {

		Iterator<HttpClientListener> it = listeners.iterator();
		while (it.hasNext()) {
			it.next().clientIdle(this, idleTime, idleCount);
		}
	}

	public boolean addClientListener(HttpClientListener listener) {
		return listeners.add(listener);
	}

	public boolean removeClientListener(HttpClientListener listener) {
		return listeners != null && listeners.remove(listener);
	}

	protected abstract void doWrite(boolean requiresClosure);
}
